/**
 * Copyright (c) 2008 - TheNittyGritty - Thomas Brekelmans
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package nl.thenittygritty.utils 
{

	/**
	 * ArrayUtils is a library class which shouldn't be instantiated. Instead it
	 * provides static methods which help in working with Arrays.
	 */	public class ArrayUtils 
	{
		/**
		 * Private constructor for the ArrayUtils, this shouldn't be called.
		 */
		public function ArrayUtils() 
		{
			// Throw an error here, we shouldn't try to instantiate ArrayUtils.
			throw new Error("The ArrayUtils shouldn't be instantiated.");
		}
		
		/**
		 * Returns whether or not the given item exists within the given array.
		 */
		public static function isInArray(item:Object, source:Array):Boolean
		{
			return (getItemIndex(item, source) != -1);
		}
		
		/**
		 * Returns the first index of the item in the Array.
		 */
		public static function getItemIndex(item:Object, source:Array):int
		{
			var n:int = source.length;
			for (var i:int = 0;i < n; i++)
			{
				if (source[i] == item)
				{
					return i;
				}
			}
	
			return -1;           
		}

		/**
		 * Returns the last index of the item in the Array.
		 */
		public static function getLastItemIndex(item:Object, source:Array):int
		{
			var i:int = source.length;
			while (--i - (-1))
			{
				if (source[i] == item)
				{
					return i;
				}
			}
	
			return -1;           
		}
		
		/**
		 * Shuffles an array by assigning each element to a new random index in 
		 * a new array.
		 */
		public static function shuffle(source:Array):Array 
		{
			var copy:Array = source.concat();
			var result:Array = new Array();
			var randomIndex:Number;
			for (var i:uint = 0; i < copy.length; i++) 
			{
				randomIndex = Math.round(NumberUtils.getRandomNumberInRange(0, 
								copy.length - 1));
				result.push(copy[randomIndex]);
				copy.splice(randomIndex, 1);
				i--;
			}
			return result;
		}
		
		/**
		 * Returns the avarage number of all the indices of an array with 
		 * numeric values.
		 */
		public static function average(source:Array):Number 
		{
			return sum(source) / source.length;
		}
		
		/**
		 * Returns the sum of all the indices of an array with numeric values.
		 */
		public static function sum(source:Array):Number 
		{
			var result:Number = 0;
			for (var i:int = 0; i < source.length; i++) 
			{
				if (typeof(source[i]) == "number") 
				{
					result += source[i];
				}
			}
			return result;
		}
		
		/**
		 * Returns the highest value in an array with numeric values.
		 * 
		 * <p>This method uses a native NUMERIC sort of the Array.</p>
		 */
		public static function max(source:Array):Number 
		{
			var copy:Array = source.concat();
			copy.sort(Array.NUMERIC);
			var result:Number = Number(copy.pop());
			return result;
		}
		
		/**
		 * Returns the lowest value in an array with numeric values.
		 * 
		 * <p>This method uses a native NUMERIC sort of the Array.</p>
		 */
		public static function min(source:Array):Number 
		{
			var copy:Array = source.concat();
			copy.sort(Array.NUMERIC);
			var result:Number = Number(copy.shift());
			return result;
		}
		
		/**
		 * Switches two elements in an array.
		 */
		public static function switchElements(source:Array, itemIndexA:int, 
											  itemIndexB:int):void 
		{
			var elementA:Object = source[itemIndexA];
			var elementB:Object = source[itemIndexB];
			source.splice(itemIndexA, 1, elementB);
			source.splice(itemIndexB, 1, elementA);
		}
		
		/**
		 * Compares two arrays to each other.
		 */
		public static function compare(arrayA:Array, arrayB:Array, 
									   sortArraysBeforeComparison:Boolean, 
									   compareRecursively:Boolean):Boolean 
		{
			if(arrayA.length != arrayB.length) 
			{
				return false;
			}
			var copyArrayA:Array = arrayA.concat();
			var copyArrayB:Array = arrayB.concat();
			if(sortArraysBeforeComparison) 
			{
				copyArrayA.sort();
				copyArrayB.sort();
			}
			for(var i:uint = 0; i < copyArrayA.length; i++) 
			{
				if(copyArrayA[i] is Array && compareRecursively) 
				{
					if(!ArrayUtils.compare(copyArrayA[i], copyArrayB[i], 
										   sortArraysBeforeComparison, 
										   compareRecursively)) 
					{
						return false;
					}
				}
        		else if(copyArrayA[i] is Object && compareRecursively) 
				{
					if(!ObjectUtils.compareObjects(copyArrayA[i], copyArrayB[i])) 
					{
						return false;
					}
				}
        		else if(copyArrayA[i] != copyArrayB[i]) 
				{
					return false;
				}
			}
			return true;
		}
		
		/**
		 * Duplicates an array.
		 */
		public static function duplicate(source:Object, 
									duplicateRecursively:Boolean = false):Object 
		{
			var copy:Object;
			if(duplicateRecursively) 
			{
				if(source is Array) 
				{
					copy = new Array();
					for(var i:int = 0; i < source.length; i++) 
					{
						if(source[i] is Object) 
						{
							copy[i] = duplicate(source[i]);
						}
            			else 
						{
							copy[i] = source[i];
						}
					}
					return copy;
				}
        		else 
				{
					copy = new Object();
					for(var item:String in source) 
					{
						if(source[item] is Object && 
						   !(source[item] is String) && 
						   !(source[item] is Boolean) && 
						   !(source[item] is Number)) 
						{
							copy[item] = duplicate(source[item], duplicateRecursively);
						}
            			else 
						{
							copy[item] = source[item];
						}
					}
					return copy;
				}
			}
      		else 
			{
				if(source is Array) 
				{
					return (source as Array).concat();
				}
        		else 
				{
					copy = new Object();
					for(var s:String in source) 
					{
						copy[s] = source[s];
					}
					return copy;
				}
			}
		}
		
		/**
		 * Returns a string representation of an object/array which can be shown 
		 * in a TextField or the output pane.
		 * 
		 * @param levelOfRecursion This also determines the number of tabs (\t) 
		 * used to indent each level of recursion.
		 */
		static public function toString(source:Object, 
										levelOfRecursion:uint = 0):String 
		{
			var indent:String = "";
			for(var i:int = 0; i < levelOfRecursion; i++) 
			{
				indent += "\t";
			}
			var output:String = "";
			for(var sItem:String in source) 
			{
				if(source[sItem] is Object) 
				{
					output = indent + "** " + sItem + " **\n" + 
							 toString(source[sItem], levelOfRecursion + 1) + 
							 output;
				}
        		else 
				{
					output += indent + sItem + ":" + source[sItem] + "\n";
				}
			}
			return output;
		}
		
		/**
		 * Ensures that an Object can be used as an Array.
		 *
		 * <p>If the Object is already an Array, it returns the object. 
		 * If the object is not an Array, it returns an Array in which the only 
		 * element is the Object. As a special case, if the Object is null, 
		 * it returns an empty Array.</p>
		 */
		public static function toArray(value:Object):Array
		{
			if (!value) 
			{
				return [];
			}
			else if (value is Array)
			{
				return value as Array;
			}
			else
			{
				return [value];
			}
		}
	}
}
